package org.hackystat.ui.projectviewer.gwt.server;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.datatype.XMLGregorianCalendar;

import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.projects.jaxb.Invitations;
import org.hackystat.sensorbase.resource.projects.jaxb.Members;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectIndex;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectRef;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectSummary;
import org.hackystat.sensorbase.resource.projects.jaxb.Properties;
import org.hackystat.sensorbase.resource.projects.jaxb.Property;
import org.hackystat.sensorbase.resource.projects.jaxb.SensorDataSummary;
import org.hackystat.sensorbase.resource.projects.jaxb.UriPatterns;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.telemetry.service.client.TelemetryClient;
import org.hackystat.telemetry.service.client.TelemetryClientException;
import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryChartData;
import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryPoint;
import org.hackystat.ui.projectviewer.gwt.client.common.ProjectViewerException;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectData;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectDevSummary;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectSummaryData;
import org.hackystat.ui.projectviewer.gwt.client.common.types.ProjectViewerProperties;
import org.hackystat.ui.projectviewer.gwt.client.dataprovider.ProjectViewerDataProvider;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * Implements remote service for the ProjectService and provides asynchronous API for project
 * manipulations.
 *
 * @author Pavel Senin.
 *
 */
public class ProjectViewerDataProviderImpl extends RemoteServiceServlet implements
    ProjectViewerDataProvider {

  /** Eclipse-generated fancy serial. */
  private static final long serialVersionUID = 7583543068099234416L;

  /** Timeout constant for communication with sensorbase. */
  private static final int timeout = 20 * 1000;

  /** The properties instance. */
  private org.hackystat.ui.projectviewer.gwt.server.Properties properties;

  /** Logger. */
  private static final Logger logger = HackystatLogger
      .getLogger("org.hackystat.ui.projectviewer.gwt.projectviewer-server");

  /** Short term memory for exceptions. */
  private String exceptionMessage;

  private SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd', 'HH:mm:ss", Locale.US);

  private static final int maxRowsNum = 100;

  private static final String TAB = "\t";

  private static final String SP = "          ";

  /**
   * {@inheritDoc}
   */
  public ProjectViewerProperties getProjectViewerProperties() {
    ProjectViewerProperties res = new ProjectViewerProperties();
    try {
      this.properties = new org.hackystat.ui.projectviewer.gwt.server.Properties();
      if (this.properties == null) {
        this.exceptionMessage = "unable to find properties file";
        logger.log(Level.SEVERE, "Unable to find/read properties file");
        return null;
      }
      res.setSensorbaseHost(this.properties.getSensorbaseHost());
      res.setDPDHost(this.properties.getDailyProjectDataHost());
      res.setTelemetryHost(this.properties.getTelemetryHost());
    }
    catch (Exception e) {
      this.exceptionMessage = "unable to read properties\n " + e.getMessage();
      logger.log(Level.SEVERE, "Error while reading properties: " + e.getMessage() + '\n');
      return null;
    }
    logger.info("Properties found and parsed.");
    return res;
  }

  /**
   * Confirms sensorbase host.
   *
   * @param hostName The sensorbase host URI.
   * @return True if able to ping sensorbase, false if not.
   */
  public Boolean confirmSensorbaseHost(String hostName) {
    logger.info("setting sensorbase host as " + hostName);
    if (SensorBaseClient.isHost(hostName)) {
      logger.info("sensorbase host confirmed at " + hostName);
      return true;
    }
    else {
      this.exceptionMessage = "unable to confirm sensorbase host at " + hostName;
      logger.info("unable to confirm sensorbase host at " + hostName);
      return false;
    }
  }

  /**
   * Confirms dpd host.
   *
   * @param hostName The sensorbase host URI.
   * @return True if able to ping sensorbase, false if not.
   */
  public Boolean confirmDPDHost(String hostName) {
    logger.info("setting dpd host as " + hostName);
    if (DailyProjectDataClient.isHost(hostName)) {
      logger.info("DPD host confirmed at " + hostName);
      return true;
    }
    else {
      this.exceptionMessage = "unable to confirm DPD host at " + hostName;
      logger.info("unable to confirm DPD host at " + hostName);
      return false;
    }
  }

  /**
   * Confirms telemetry host.
   *
   * @param hostName The telemetry host URI.
   * @return True if able to ping telemetry, false if not.
   */
  public Boolean confirmTelemetryHost(String hostName) {
    logger.info("setting telemetry host as " + hostName);
    if (TelemetryClient.isHost(hostName)) {
      logger.info("Telemetry host confirmed at " + hostName);
      return true;
    }
    else {
      this.exceptionMessage = "unable to confirm telemetry host at " + hostName;
      logger.info("unable to confirm Telemetry host at " + hostName);
      return false;
    }
  }

  /**
   * Confirms that user is authorized at the sensorbase host.
   *
   * @param hostName The sensorbase host.
   * @param user The user name.
   * @param password The password.
   * @return True if the client authorized or false if not.
   */
  public Boolean confirmUser(String hostName, String user, String password) {
    if (SensorBaseClient.isRegistered(hostName, user, password)) {
      logger.info("confirmed user " + user + ":" + password + " at  " + hostName);
      return true;
    }
    else {
      this.exceptionMessage = "unable to confirm user " + user + ":" + password + " at  "
          + hostName;
      logger.info(this.exceptionMessage);
      return false;
    }
  }

  /**
   * Reports last seen exception message.
   *
   * @return The Exception message.
   */
  public String getExceptionMessage() {
    if (null == this.exceptionMessage) {
      return "";
    }
    else {
      return this.exceptionMessage;
    }
  }

  /**
   * Reports list of all projects for authorized user.
   *
   * @param host The sensorbase host.
   * @param user The user name.
   * @param password The password.
   *
   * @return The project index.
   */
  public List<String> getProjectIndex(String host, String user, String password) {
    logger.info("getting projects list for the user " + user + " at " + host);
    List<String> res = new ArrayList<String>();
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, password);
      client.authenticate();
      client.setTimeout(timeout);
      ProjectIndex index = client.getProjectIndex(user);
      logger.info("client.getProjectIndex(" + user + ") reported " + index.getProjectRef().size()
          + " projects:  ");
      logger.info(SP + "CLIENT TIMEOUT: " + timeout + " msec ");
      for (ProjectRef ref : index.getProjectRef()) {
        // logger.info(SP + "about to run client.getProject(ref) for " + ref.getName());
        Project p = client.getProject(ref);
        // logger.info(SP + "retrieved project " + p.getName());
        if ((!p.getInvitations().getInvitation().isEmpty())
            && (p.getInvitations().getInvitation().contains(user))) {
          // logger.info(SP + p.getName() + " INVITATION ");
          continue;
        }
        res.add(ref.getName());
        // logger.info(SP + p.getName() + " ADDED TO LIST ");
      }
      return res;
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while retrieving project index for user " + user + ":" + password
          + " at host " + host + " error: " + this.exceptionMessage
          + ", exception stored locally for later client retrieval.");
    }
    return null;
  }

  /**
   * Reports project data.
   *
   * @param host The sensorbase host.
   * @param user The user name.
   * @param password The password.
   * @param projectName The project name.
   * @return ProjectData The project data.
   */
  public ProjectData getProjectData(String host, String user, String password, String projectName) {
    logger.info("getting " + projectName + " info for user : " + user + " from sensorbase " + host);
    // the brand new empty project data
    ProjectData pd = new ProjectData();
    try {
      // getting Project
      SensorBaseClient client = new SensorBaseClient(host, user, password);
      client.setTimeout(timeout);
      ProjectIndex index = client.getProjectIndex(user);
      logger.info("client.getProjectIndex(" + user + ") reported " + index.getProjectRef().size()
          + " projects:  ");
      for (ProjectRef ref : index.getProjectRef()) {
        logger.info(SP + ref.getName() + " owned by " + ref.getOwner());
      }
      Project prj = null;
      for (ProjectRef pr : index.getProjectRef()) {
        if (projectName.equalsIgnoreCase(pr.getName())) {
          prj = client.getProject(pr);
          break;
        }
      }
      if (null == prj) {
        return null;
      }
      else {
        // converting into ProjectData, name, owner and description
        pd.setName(prj.getName());
        pd.setDescription(prj.getDescription());
        pd.setOwner(prj.getOwner());
        // start, end and modification time
        pd.setStartTime(new Date(prj.getStartTime().toGregorianCalendar().getTimeInMillis()));
        pd.setEndTime(new Date(prj.getEndTime().toGregorianCalendar().getTimeInMillis()));
        pd.setLastMod(new Date(prj.getLastMod().toGregorianCalendar().getTimeInMillis()));
        // members
        if (null != prj.getMembers()) {
          pd.setMembers(prj.getMembers().getMember());
        }
        // properties
        if (null != prj.getProperties()) {
          List<Property> prop = prj.getProperties().getProperty();
          HashMap<String, String> pr = new HashMap<String, String>();
          if (!prop.isEmpty()) {
            for (Property p : prop) {
              pr.put(p.getKey(), p.getValue());
            }
          }
          pd.setProperties(pr);
        }
        // uri patterns, note that we always have at least
        if (null != prj.getUriPatterns()) {
          pd.setUriPatterns(prj.getUriPatterns().getUriPattern());
        }
        // and finally invitations
        if (null != prj.getInvitations()) {
          pd.setInvitations(prj.getInvitations().getInvitation());
        }
      }
      return pd;
    }
    catch (ProjectViewerException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while parsing project data: " + e.getMessage());
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while retrieving project index: " + e.getMessage());
    }
    return null;
  }

  /**
   * Puts project.
   *
   * @param host The sensorbase host.
   * @param user The user name.
   * @param password The password.
   * @param pd The project data object.
   *
   * @return The PUT operation result.
   */
  public Boolean putProjectData(String host, String user, String password, ProjectData pd) {
    logger.info("creating new project " + pd.getName() + " for owner " + pd.getOwner());

    Project np = new Project();

    logger.info("project name: " + pd.getName());
    np.setName(pd.getName());

    logger.info("project owner: " + pd.getOwner());
    np.setOwner(pd.getOwner());

    logger.info("project description: " + pd.getDescription());
    np.setDescription(pd.getDescription());

    logger.info("project start time: " + pd.getStartTime().toString());
    np.setStartTime(Tstamp.makeTimestamp(pd.getStartTime().getTime()));

    logger.info("project end time: " + pd.getEndTime().toString());
    np.setEndTime(Tstamp.makeTimestamp(pd.getEndTime().getTime()));

    Members mem = new Members();
    List<String> pMembers = pd.getMembers();
    Iterator<String> itr = pMembers.iterator();
    while (itr.hasNext()) {
      String m = itr.next();
      logger.info("project member: " + m);
      mem.getMember().add(m);
    }
    np.setMembers(mem);

    UriPatterns up = new UriPatterns();
    List<String> uPatterns = pd.getUriPatterns();
    itr = uPatterns.iterator();
    while (itr.hasNext()) {
      String u = itr.next();
      logger.info("project uri: " + u);
      up.getUriPattern().add(u);
    }
    np.setUriPatterns(up);

    Properties prop = new Properties();
    Map<String, String> p = pd.getProperties();
    if (!p.isEmpty()) {
      Iterator<Entry<String, String>> entryItr = p.entrySet().iterator();
      while (entryItr.hasNext()) {
        Entry<String, String> entry = entryItr.next();
        logger.info("project property: " + entry.getKey() + " : " + entry.getValue());
        Property pr = new Property();
        pr.setKey(entry.getKey());
        pr.setValue(entry.getValue());
        prop.getProperty().add(pr);
      }
    }
    np.setProperties(prop);

    // work on invitations here
    List<String> invitations = pd.getInvitations();
    // here we patching project
    if (null == np.getInvitations()) {
      np.setInvitations(new Invitations());
    }
    for (String invitee : invitations) {
      logger.info("inviting: " + invitee);
      np.getInvitations().getInvitation().add(invitee);
    }

    // saving to sensorbase
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, password);
      client.setTimeout(timeout);
      client.putProject(np);
      logger.info("succesfully PUT new project to sensorbase.");
      return true;
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("unable to PUT new project into sensorbase:" + this.exceptionMessage);
    }
    return false;
  }

  /**
   *
   * Removes the project.
   *
   * @param host The sensorbase host.
   * @param user The user name.
   * @param password The password.
   * @param projectName Thep roject name.
   *
   * @return The deletion result.
   */
  public Boolean deleteProject(String host, String user, String password, String projectName) {
    logger.info("deleting the project : " + projectName);
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, password);
      client.setTimeout(timeout);
      // check if able to delete this project first
      ProjectIndex index = client.getProjectIndex(user);
      for (ProjectRef ref : index.getProjectRef()) {
        if (projectName.equalsIgnoreCase(ref.getName())) {
          if (user.equalsIgnoreCase(ref.getOwner())) {
            client.deleteProject(user, projectName);
            return true;
          }
          else {
            this.exceptionMessage = "Unable to delete project " + projectName + " only owner "
                + ref.getOwner() + " is able to delete this project.";
          }
        }
      }
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while deleting the project : " + projectName + " : "
          + this.exceptionMessage);
    }
    return false;
  }

  /**
   * Registers new user with sensorbase host.
   *
   * @param host The sensorbase host.
   * @param user The user e-mail.
   * @return The registartion result.
   */
  public Boolean registerUser(String host, String user) {
    logger.info("registering new user: " + user + " at " + host);
    // if host is alive - register user
    try {
      if (SensorBaseClient.isHost(host)) {
        SensorBaseClient.registerUser(host, user);
        logger.info("user " + user + " registered at " + host);
        return true;
      }
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while registering new user: " + user + " at " + host + ":"
          + this.exceptionMessage);
    }
    return false;
  }

  /**
   * Invites user to the project
   *
   * @param host The sensorbase host.
   * @param user The user e-mail.
   * @param pass The user password.
   * @param projectId The project to invite user into.
   * @param invitee The invitee e-mail.
   * @return True if succeeded, False if not.
   */
  public Boolean invite(String host, String user, String pass, String projectId, String invitee) {
    logger.info("inviting user: " + invitee + " to the project " + projectId + " owned by " + user);
    // if host is alive - do it
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, pass);
      Project prj = client.getProject(user, projectId);
      if (null != prj) {
        prj.getInvitations().getInvitation().add(invitee);
        client.putProject(prj);
        return true;
      }
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while inviting user: " + this.exceptionMessage);
    }
    return false;
  }

  /**
   * Reports a list of projects user is invited into.
   *
   * @param host The sensorbase host.
   * @param user The user name.
   * @param pass The user password.
   * @return The list of projects user invited to join.
   */
  public List<String> getInvitationsList(String host, String user, String pass) {
    logger.info("Getting list of invitations for the user " + user + " at the  " + host);
    ArrayList<String> res = new ArrayList<String>();
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, pass);
      ProjectIndex prjIdx = client.getProjectIndex(user);
      logger.info("found " + prjIdx.getProjectRef().size() + " projects for " + user + " at the  "
          + host);
      for (ProjectRef prjRef : prjIdx.getProjectRef()) {
        Project pd = client.getProject(prjRef);
        // now check who's owner
        String owner = pd.getOwner();
        if (owner.equalsIgnoreCase(user)) {
          logger.info(pd.getName() + " found to be own by " + user + " at the  " + host);
          // do nothing
          assert true;
        }
        else {
          logger.info(pd.getName() + " found not to be own by " + user + " at the  " + host);
          // check if user is in the invitations list
          Invitations invts = pd.getInvitations();
          if (invts.getInvitation().contains(user)) {
            logger.info(user + " found to be invited to project " + pd.getName() + " at the  "
                + host);
            res.add(pd.getName());
          }
          else {
            // user is a member, we are not interested in this now
            assert true;
          }
        }
      }
    }
    catch (SensorBaseClientException e) {
      res = null;
      this.exceptionMessage = e.getMessage();
      logger.info("exception while getting invitations: " + this.exceptionMessage);
    }
    return res;
  }

  /**
   * {@inheritDoc}
   */
  public Boolean acceptInvitation(String host, String user, String password, String projectId) {
    logger.info("User " + user + " accepts invitation to join the project " + projectId);
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, password);
      client.setTimeout(timeout);
      ProjectIndex idx = client.getProjectIndex(user);
      for (ProjectRef pr : idx.getProjectRef()) {
        if (projectId.equalsIgnoreCase(pr.getName())) {
          String owner = pr.getOwner();
          client.reply(owner, projectId, SensorBaseClient.InvitationReply.ACCEPT);
          return true;
        }
      }
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while accepting invitation: " + this.exceptionMessage);
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  public Boolean declineInvitation(String host, String user, String password, String projectId) {
    logger.info("User " + user + " declines invitation to join the project " + projectId);
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, password);
      client.setTimeout(timeout);
      ProjectIndex idx = client.getProjectIndex(user);
      for (ProjectRef pr : idx.getProjectRef()) {
        if (projectId.equalsIgnoreCase(pr.getName())) {
          String owner = pr.getOwner();
          client.reply(owner, projectId, SensorBaseClient.InvitationReply.DECLINE);
          return true;
        }
      }
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while accepting invitation: " + this.exceptionMessage);
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  public String[] getSensorData(String host, String user, String password, int startRow,
      int maxRows, String projectName, String projectOwner) {
    logger.info("getting sensordata index for the project " + projectName + " at host " + host
        + " for user " + user);
    ArrayList<String> result = new ArrayList<String>();
    try {
      List<SensorDataRef> si = getProjectSensorDataIndex(host, user, password, projectName,
          projectOwner);
      if (null != si) {
        int counter = 0;
        int index = 0;
        for (SensorDataRef ref : si) {
          if (counter < startRow) {
            counter++;
          }
          else if (index < maxRows) {
            String sdtType = ref.getSensorDataType();
            String tool = ref.getTool();
            String sdOwner = ref.getOwner();
            XMLGregorianCalendar lastMod = ref.getTimestamp();
            result.add(sdtType + TAB + tool + TAB + sdOwner + TAB
                + dtf.format(lastMod.toGregorianCalendar().getTime()));
            index++;
          }
          else {
            break;
          }
        }
      }
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while retrieving project index: " + this.exceptionMessage);
    }
    String[] res = new String[result.size()];
    return result.toArray(res);
  }

  /**
   * Reports a list of sensor data references.
   *
   * @param host The sensorbase host.
   * @param user The user name.
   * @param password The password.
   * @param projectName The project name to get data for.
   * @param projectOwner The projectOwner.
   * @return a list of sensor data references.
   * @throws SensorBaseClientException if error encountered.
   */
  List<SensorDataRef> getProjectSensorDataIndex(String host, String user, String password,
      String projectName, String projectOwner) throws SensorBaseClientException {

    // blessing the client and getting project index
    SensorBaseClient client = new SensorBaseClient(host, user, password);
    client.setTimeout(timeout);
    client.enableHttpTracing(true);

    Project prj = client.getProject(projectOwner, projectName);

    // getting 100 or less of project data instances for browsing
    if (prj == null) {
      return null;
    }
    else {
      try {
        XMLGregorianCalendar projectStartDate = Tstamp.incrementSeconds(prj.getStartTime(), +1);
        XMLGregorianCalendar projectLastDay = Tstamp.incrementSeconds(prj.getEndTime(), -1);
        XMLGregorianCalendar currentDate = Tstamp.incrementSeconds(Tstamp.makeTimestamp(), -1);

        XMLGregorianCalendar intervalEnd = currentDate;

        if (Tstamp.greaterThan(intervalEnd, projectLastDay)) {
          intervalEnd = projectLastDay;
        }

        logger.info("getting summary for " + projectName + " owner " + prj.getOwner() + " at host "
            + host + " for user " + user + " time period: "
            + Tstamp.makeTimestamp(projectStartDate).toString() + " to "
            + Tstamp.makeTimestamp(intervalEnd).toString());

        ProjectSummary ps = client.getProjectSummary(prj.getOwner(), projectName, projectStartDate,
            intervalEnd);

        logger.info("--> debuggy, got summary: " + ps.getSensorDataSummaries().getNumInstances()
            + " instances found.");

        int minValue = 0;
        int maxValues = maxRowsNum;
        if (ps.getSensorDataSummaries().getNumInstances().intValue() > maxRowsNum) {
          minValue = ps.getSensorDataSummaries().getNumInstances().intValue() - maxRowsNum;
        }
        else {
          minValue = 0;
          maxValues = ps.getSensorDataSummaries().getNumInstances().intValue();
        }
        SensorDataIndex index = client.getProjectSensorData(prj.getOwner(), projectName,
            projectStartDate, intervalEnd, minValue, maxValues);

        logger.info("got " + index.getSensorDataRef().size() + " entries for iteration 0");

        List<SensorDataRef> refList = index.getSensorDataRef();
        Collections.sort(refList, new SensorDataRefComparator<SensorDataRef>());
        return refList;
      }
      catch (SensorBaseClientException e) {
        this.exceptionMessage = e.getMessage();
        logger.info("exception while retrieving project summary for user " + user + ":" + password
            + " at host " + host + " error: " + this.exceptionMessage
            + ", exception stored locally for later client retrieval.");
      }
      return null;
    }
  }

  /**
   * Compares {@link SensorDataRef} instances by the last modified time.
   *
   * @author Pavel Senin.
   *
   * @param <T> type of comparable instances.
   */
  private static class SensorDataRefComparator<T> implements Comparator<SensorDataRef>,
      Serializable {

    /**
     * Serial id.
     */
    private static final long serialVersionUID = -6681813819081834358L;

    /**
     * {@inheritDoc}
     */
    public int compare(SensorDataRef ref1, SensorDataRef ref2) {
      // was using getLastMod() before
      XMLGregorianCalendar lastMod1 = ref1.getTimestamp();
      XMLGregorianCalendar lastMod2 = ref2.getTimestamp();
      if ((null == lastMod1) || (null == lastMod2)) {
        logger
            .info("ERROR in comparator lastMod1:  --> " + lastMod1 + "lastMod2:  --> " + lastMod1);
        return 0;
      }
      else {
        return lastMod2.compare(lastMod1);
      }
    }
  }

  /**
   * Reports project summary.
   *
   * @param sensorBaseHost The sensorbase host.
   * @param dpdHost The DPD host.
   * @param user The user name.
   * @param password The password.
   * @param projectId The project name.
   * @return The project summary.
   */
  public ProjectSummaryData getProjectSummaryData(String sensorBaseHost, String dpdHost,
      String user, String password, String projectId) {
    logger.info("getting " + projectId + " summary for user : " + user + " from sensorbase "
        + sensorBaseHost);
    // the brand new empty project data
    ProjectSummaryData psd = new ProjectSummaryData();

    SensorBaseClient client = new SensorBaseClient(sensorBaseHost, user, password);
    try {
      // getting Project
      ProjectIndex idx = client.getProjectIndex(user);
      Project prj = null;
      for (ProjectRef pr : idx.getProjectRef()) {
        if (projectId.equalsIgnoreCase(pr.getName())) {
          prj = client.getProject(pr);
          break;
        }
      }
      if (null == prj) {
        return null;
      }
      // don't forget than
      // exception while retrieving project index: 400:
      // endTime cannot be greater than the project's end time.
      ProjectSummary ps = client.getProjectSummary(prj.getOwner(), projectId, prj.getStartTime(),
          prj.getEndTime());
      psd.setDataInstances(ps.getSensorDataSummaries().getNumInstances().intValue());
      HashMap<String, String> dataInstances = new HashMap<String, String>();
      List<SensorDataSummary> sds = ps.getSensorDataSummaries().getSensorDataSummary();
      for (SensorDataSummary s : sds) {
        if (s.isSetSensorDataType() && s.isSetNumInstances()) {
          dataInstances.put(s.getSensorDataType(), s.getNumInstances().toString());
        }
      }
      psd.setDataSummary(dataInstances);
      logger.info("successfully packed summary for the project " + projectId);
    }
    catch (SensorBaseClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while retrieving project summary for user " + user + ":" + password
          + " at host " + sensorBaseHost + " error: " + this.exceptionMessage
          + ", exception stored locally for later client retrieval.");
    }
    return psd;
  }

  /**
   * Gets the project development data summary.
   *
   * @param telemetryHost The telemetry host.
   * @param user The user name.
   * @param password The password.
   * @param projectId The project name.
   * @param owner The project owner.
   * @return the project data development summary.
   */
  public ProjectDevSummary getProjectDevTimeData(String telemetryHost, String user,
      String password, String projectId, String owner) {
    logger.info("getting " + projectId + " dev time dpd for user : " + user
        + " from telemetry host " + telemetryHost);
    // the brand new empty project data
    ProjectDevSummary pds = new ProjectDevSummary();
    try {
      // and host client
      TelemetryClient client = new TelemetryClient(telemetryHost, user, password);
      // making time interval
      XMLGregorianCalendar currTstamp = Tstamp.makeTimestamp();
      currTstamp.setHour(0);
      currTstamp.setMinute(0);
      currTstamp.setSecond(0);
      currTstamp.setMillisecond(0);
      XMLGregorianCalendar endTstamp = Tstamp.incrementDays(currTstamp, 1);
      XMLGregorianCalendar startTstamp = Tstamp.incrementDays(endTstamp, -7);

      startTstamp = Tstamp.incrementSeconds(startTstamp, 1);
      endTstamp = Tstamp.incrementSeconds(endTstamp, -1);

      TelemetryChartData cd = client.getChart("DevTime", owner, projectId, "Day", startTstamp,
          endTstamp, user + ",false");

      TreeMap<Date, String> data = new TreeMap<Date, String>();
      for (TelemetryPoint p : cd.getTelemetryStream().get(0).getTelemetryPoint()) {
        if (null == p.getValue()) {
          data.put(p.getTime().toGregorianCalendar().getTime(), "0.0");
        }
        else {
          data.put(p.getTime().toGregorianCalendar().getTime(), p.getValue());
        }
      }
      Float[] values = new Float[data.size()];
      String[] caps = new String[data.size()];
      SimpleDateFormat format = new SimpleDateFormat("MMM-dd", Locale.US);
      int i = 0;
      for (Entry<Date, String> e : data.entrySet()) {
        caps[i] = format.format(e.getKey());
        values[i] = Float.valueOf(e.getValue());
        i++;
      }
      pds.setData(caps, values);
      logger.info("successfully packed summary for the project " + projectId);
    }
    catch (TelemetryClientException e) {
      this.exceptionMessage = e.getMessage();
      logger.info("exception while retrieving project summary for user " + user + ":" + password
          + " at host " + telemetryHost + " error: " + this.exceptionMessage
          + ", exception stored locally for later client retrieval.");
    }
    return pds;
  }

  /**
   * Fires data download.
   *
   * @param sensorbaseHost The sensorbase host.
   * @param user The user name.
   * @param password The password.
   * @param projectId The project name.
   * @param sdtType The sdt type.
   * @return Success (true) if was able to fire download.
   */
  public Boolean fireDataDownload(String sensorbaseHost, String user, String password,
      String projectId, String sdtType) {
    logger.info("firing data download for " + projectId);
    return true;
  }

}
